package com.edol.painting.widget;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.Shader.TileMode;
import android.graphics.SweepGradient;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

import com.doodle.common.utils.Utils;
import com.edol.painting.R;
import com.edol.painting.utils.Constant;

public class ColorPickerView extends View {

    public interface OnColorChangedListener {
        void colorChanged(int color);
        void onColorClicked(int color);
    }

    private RectF mRingRectF = new RectF();
    private RectF mTouchRectF = new RectF();
    private Paint mRingPaint;

    private static final int[] sRingColors = new int[] { 0xFFFF0000,
            0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF, 0xFF00FF00, 0xFFFFFF00,
            0xFFFF0000 };

    private final int mRoundRadius = getResources().getDimensionPixelSize(R.dimen.seekbar_round_angle);

    private int mRingRadius = 180;
    private int mRingWidth = 50;
    private int mInnerRingRadius = 160;

    private int mSquareColor = 0xFF000000;

    private float mRingUnit = 0;
    private float mSaturationRatio = 0.8f;
    private float mLightnessRatio = 0.8f;

    private int mResultColor = 0xFF000000;
    private int mCurrentColor = 0xFF000000;
    
    private float mOvalCenterX = 0;
    private float mOvalCenterY = 0;

    private Paint mColorPaint;

    private boolean mInSaturRect;
    private boolean mInLightRect;

    private float mSaturX;
    private float mLightX;

    private RectF mIndicatorRect = new RectF();
    private Bitmap mIndicatorBitmap = null;

    private OnColorChangedListener mListener;

    private boolean mTouchMoved = false;

    private float mRingAngle = 0;

    private float mLastRingAngle = 0;
    private float mLastSaturX = 0;
    private float mLastLightX = 0;

    private float mTouchDownX = 0;
    private float mTouchDownY = 0;
    private float mTouchSlope = 4;

    private Paint mEdgePaint;
    private Paint mLightPaint;
    private Paint mAlphaPaint;
    private int mSeekBarW = 400;
    private int mSeekBarH = 45;
    private RectF mSaturBarRect = new RectF();
    private RectF mLightBarRect = new RectF();

    private RectF mSaturBarRealRect = new RectF();
    private RectF mLightBarRealRect = new RectF();

    public ColorPickerView(Context context) {
        super(context);
        initView();
    }

    public ColorPickerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public ColorPickerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView();
    }

    public void initView() {
        Shader s = new SweepGradient(0, 0, sRingColors, null);
        
        Resources res = getResources();
        mIndicatorBitmap = BitmapFactory.decodeResource(res, R.drawable.seek_point);
        mRingWidth = mIndicatorBitmap.getWidth();
        
        mRingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mRingPaint.setShader(s);
        mRingPaint.setStyle(Style.STROKE);
        mRingPaint.setStrokeWidth(mRingWidth - 5);

        mLightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mAlphaPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mEdgePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mEdgePaint.setColor(Color.GRAY);
        mEdgePaint.setStyle(Style.STROKE);

        mColorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        mTouchSlope = ViewConfiguration.get(getContext()).getScaledTouchSlop();
    }

    public void setOnColorChangedListener(OnColorChangedListener l) {
        mListener = l;
    }

    public void setCurrentColor(int color, boolean update) {
        if (update) {
            mResultColor = color;

            int r = Color.red(color);
            int g = Color.green(color);
            int b = Color.blue(color);

            if (r == 0 && g == 0 && b == 0) {
                mSaturationRatio = 0;
                mLightnessRatio = 0;

                mRingUnit = 0;
                mSquareColor = interpColor(sRingColors, 0);
                setSaturBarColor();

                mRingAngle = 0;

                mSaturX = mSaturBarRect.right;
                mLightX = mLightBarRect.right;
                return;
            }

            int index = 0;
            float diff = 0;

            float squareUnitX = 1;
            float squareUnitY = 0;

            if (r >= b && g >= b) {
                if (r >= g) {
                    index = 5;
                    squareUnitY = (float) (255 - r) / 255;
                    squareUnitX = 1 - b / ((1 - squareUnitY) * 255);
                    r = 255;
                    b = 0;
                    g = Math.round(255 - ((255 - g / (1 - squareUnitY)) / squareUnitX));
                    diff = 255 - g;
                } else {
                    index = 4;
                    squareUnitY = (float) (255 - g) / 255;
                    squareUnitX = 1 - (float) b / ((1 - squareUnitY) * 255);
                    g = 255;
                    b = 0;
                    r = Math.round(255 - ((255 - r / (1 - squareUnitY)) / squareUnitX));
                    diff = r;
                }
            } else if (r >= g && b >= g) {
                if (r >= b) {
                    index = 0;
                    squareUnitY = (float) (255 - r) / 255;
                    squareUnitX = 1 - g / ((1 - squareUnitY) * 255);
                    r = 255;
                    g = 0;
                    b = Math.round(255 - ((255 - b / (1 - squareUnitY)) / squareUnitX));
                    diff = b;
                } else {
                    index = 1;
                    squareUnitY = (float) (255 - b) / 255;
                    squareUnitX = 1 - g / ((1 - squareUnitY) * 255);
                    b = 255;
                    g = 0;
                    r = Math.round(255 - ((255 - r / (1 - squareUnitY)) / squareUnitX));
                    diff = 255 - r;
                }
            } else {
                if (g >= b) {
                    index = 3;
                    squareUnitY = (float) (255 - g) / 255;
                    squareUnitX = 1 - r / ((1 - squareUnitY) * 255);
                    g = 255;
                    r = 0;
                    b = Math.round(255 - ((255 - b / (1 - squareUnitY)) / squareUnitX));
                    diff = 255 - b;
                } else {
                    index = 2;
                    squareUnitY = (float) (255 - b) / 255;
                    squareUnitX = 1 - r / ((1 - squareUnitY) * 255);
                    b = 255;
                    r = 0;
                    g = Math.round(255 - ((255 - g / (1 - squareUnitY)) / squareUnitX));
                    diff = g;
                }
            }

            mSaturationRatio = 1 - squareUnitX;
            mLightnessRatio = squareUnitY;

            Log.i("pickerview", "setCurrentColor()--mSaturationRatio="+mSaturationRatio+",mLightnessRatio="+mLightnessRatio);

            mRingUnit = (index + diff / 255) / 6;
            mSquareColor = Color.argb(255, r, g, b);
            setSaturBarColor();

            mRingAngle = mRingUnit * 2 * Constant.PI;

            mSaturX = mSaturBarRect.left + (mSeekBarW - mSeekBarH) * (1 - mSaturationRatio);
            mLightX = mLightBarRect.left + (mSeekBarW - mSeekBarH) * (1 - mLightnessRatio);
        } else {
        	mCurrentColor = color;
            invalidate();
        }
    }

    public void setCurrentColor(float ringUnit, float squareUnitX, float squareUnitY) {
        mRingUnit = ringUnit;
        mSaturationRatio = squareUnitX;
        mLightnessRatio = squareUnitY;
        mSquareColor = interpColor(sRingColors, mRingUnit);
        mResultColor = calcResultColor(mSquareColor, mSaturationRatio, mLightnessRatio);
        mRingAngle = mRingUnit * 2 * Constant.PI;
        setSaturBarColor();

        mSaturX = mSaturBarRect.left + (mSeekBarW - mSeekBarH) * (1 - mSaturationRatio);
        mLightX = mLightBarRect.left + (mSeekBarW - mSeekBarH) * (1 - mLightnessRatio);
    }
    
    public void setOldColor(float ringUnit, float squareUnitX, float squareUnitY) {
        mRingUnit = ringUnit;
        mSaturationRatio = squareUnitX;
        mLightnessRatio = squareUnitY;
        mSquareColor = interpColor(sRingColors, mRingUnit);
        mCurrentColor = calcResultColor(mSquareColor, mSaturationRatio, mLightnessRatio);
    }
    
    public int getSpecifyColor(float ringUnit, float saturation, float lightness) {
    	int squareColor = interpColor(sRingColors, ringUnit);
    	int color = calcResultColor(squareColor, saturation, lightness);
    	return color;
    }
    
    public int getOldColor() {
    	return mCurrentColor;
    }

    public int getResultColor() {
        return mResultColor;
    }
    
    public float getRingUnit() {
        return mRingUnit;
    }

    public float getSquareUnitX() {
        Log.i("pickerview", "getSquareUnitX()--mSaturationRatio="+mSaturationRatio);
        return mSaturationRatio;
    }

    public float getSquareUnitY() {
        Log.i("pickerview", "getSquareUnitY()--mLightnessRatio="+mLightnessRatio);
        return mLightnessRatio;
    }

    public void setColorRingDimen(int radius, int innerRadius) {
        mRingRadius = radius;
        mInnerRingRadius = innerRadius;

        float r = mRingRadius - mRingWidth * 0.5f;
        mRingRectF.set(-r, -r, r, r);
        mTouchRectF.set(-r - 30, -r - 30, r + 30, r + 30);
        mOvalCenterX = mRingRectF.left + (mRingRectF.right - mRingRectF.left) / 2;
        mOvalCenterY = mRingRectF.top + (mRingRectF.bottom - mRingRectF.top) / 2;
    }

    public void setSeekBarSize(int width, int height) {
        mSeekBarW = width;
        mSeekBarH = height;

        int sbPadV = getResources().getDimensionPixelSize(R.dimen.painting_edit_color_pick_seekbar_center_distance);
        int sbPadL = getResources().getDimensionPixelSize(R.dimen.painting_edit_color_pick_ring_to_seekbar_distance);

        float centerY = mRingRectF.top + mRingRadius;

        float left = mRingRectF.right + sbPadL + mRingWidth * 0.5f;
        float right = left + mSeekBarW;
        float topS = centerY - sbPadV - mSeekBarH;
        float bottomS = topS + mSeekBarH;
        float topL = centerY + sbPadV - mSeekBarH;
        float bottomL = topL + mSeekBarH;

        mSaturBarRect.set(left, topS, right - mSeekBarH, bottomS);
        mLightBarRect.set(left, topL, right - mSeekBarH, bottomL);

        mSaturX = left;
        mLightX = left;

        mSaturBarRealRect.set(left, topS, right, bottomS);
        mLightBarRealRect.set(left, topL, right, bottomL);

        Shader lightShader = new LinearGradient(left - 60, topL, mLightBarRealRect.right + 10, topL, Color.BLACK, Color.WHITE, TileMode.REPEAT);
        mLightPaint.setShader(lightShader);
        
        setSaturBarColor();
    }
    
    private void setSaturBarColor() {
    	 int rightColor = calcResultColor(mSquareColor, 0, 0);
         int leftColor = calcResultColor(mSquareColor, 1.0f, 0);
         Shader alphaShader = new LinearGradient(mSaturBarRealRect.left - 60, mSaturBarRealRect.top, mSaturBarRealRect.right + 10, mSaturBarRealRect.top, leftColor, rightColor, TileMode.REPEAT);
         mAlphaPaint.setShader(alphaShader);
    }
    
    /*public void setIconDimen(int padding, float radius) {
        mIconPadding = padding;
        mIconRadius = radius;
        float width = mRingRadius * 2 + getPaddingLeft() + getPaddingRight();
        mIconCenterX = width - mIconPadding - mIconRadius
                - (mRingRadius + getPaddingLeft());
        mIconCenterY = mIconPadding + mIconRadius
                - (mRingRadius + getPaddingTop());
    }*/

    @Override
    protected void onDraw(Canvas canvas) {
        float centerX = mRingRadius + getPaddingLeft();
        float centerY = mRingRadius + getPaddingTop();

        canvas.translate(centerX, centerY);

        // draw outer ring
        canvas.drawOval(mRingRectF, mRingPaint);

        // draw inner ring
        canvas.save(Canvas.CLIP_SAVE_FLAG);
        canvas.clipRect(mOvalCenterX - mInnerRingRadius, mOvalCenterY - mInnerRingRadius, mOvalCenterX, mOvalCenterY + mInnerRingRadius);
        mColorPaint.setColor(mResultColor);
        canvas.drawCircle(mOvalCenterX, mOvalCenterY, mInnerRingRadius, mColorPaint);
        canvas.restore();

        canvas.save(Canvas.CLIP_SAVE_FLAG);
        canvas.clipRect(mOvalCenterX, mOvalCenterY - mInnerRingRadius, mOvalCenterX + mInnerRingRadius, mOvalCenterY + mInnerRingRadius);
        mColorPaint.setColor(mCurrentColor);
        canvas.drawCircle(mOvalCenterX, mOvalCenterY, mInnerRingRadius, mColorPaint);
        canvas.restore();

        //float halfSize = mIndicatorSize / 2;
        float halfSize = mRingWidth / 2;
        float radius = mRingRectF.top;
        float cx = (float)(-radius * Math.cos(mRingAngle));
        float cy = (float)(-radius * Math.sin(mRingAngle));
        // draw outer rect Indicator
        mIndicatorRect.left = cx - halfSize;
        mIndicatorRect.top = cy - halfSize;
        mIndicatorRect.right = cx + halfSize;
        mIndicatorRect.bottom = cy + halfSize;
        canvas.drawBitmap(mIndicatorBitmap, null, mIndicatorRect, null);

        // draw saturation seekbar
        mColorPaint.setColor(mResultColor);
        canvas.drawRoundRect(mSaturBarRealRect, mRoundRadius, mRoundRadius, mColorPaint);
        canvas.drawRoundRect(mSaturBarRealRect, mRoundRadius, mRoundRadius, mAlphaPaint);
        canvas.drawRoundRect(mSaturBarRealRect, mRoundRadius, mRoundRadius, mEdgePaint);

        // draw saturation indicator bitmap
        mIndicatorRect.top = mSaturBarRect.top;
        mIndicatorRect.bottom = mSaturBarRect.bottom;
        mIndicatorRect.left = mSaturX;
        mIndicatorRect.right = mSaturX + mSeekBarH;
//        canvas.drawBitmap(mIndicatorBitmap, mSaturX, mIndicatorRect.top, null);
        canvas.drawBitmap(mIndicatorBitmap, null, mIndicatorRect, null);

        // draw lightness seekbar
        canvas.drawRoundRect(mLightBarRealRect, mRoundRadius, mRoundRadius, mLightPaint);
        canvas.drawRoundRect(mLightBarRealRect, mRoundRadius, mRoundRadius, mEdgePaint);

        // draw lightness indicator bitmap
        mIndicatorRect.top = mLightBarRect.top;
        mIndicatorRect.bottom = mLightBarRect.bottom;
        mIndicatorRect.left = mLightX;
        mIndicatorRect.right = mLightX + mSeekBarH;
//        canvas.drawBitmap(mIndicatorBitmap, mLightX, mIndicatorRect.top, null);
        canvas.drawBitmap(mIndicatorBitmap, null, mIndicatorRect, null);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getPaddingLeft() + mRingRadius * 2 + getResources().getDimensionPixelSize(R.dimen.painting_edit_color_pick_ring_to_seekbar_distance)
                + (int)mSaturBarRealRect.width() + getPaddingRight(), mRingRadius * 2
                + getPaddingTop() + getPaddingBottom());
    }

    private static int floatToByte(float x) {
        int n = Math.round(x);
        return n;
    }

    private static int pinToByte(int n) {
        if (n < 0) {
            n = 0;
        } else if (n > 255) {
            n = 255;
        }
        return n;
    }

    private static int ave(int s, int d, float p) {
        return s + Math.round(p * (d - s));
    }

    private static float ave(float s, float d, float p) {
        return s + p * (d - s);
    }

    private static int interpColor(int colors[], float unit) {
        if (unit <= 0) {
            return colors[0];
        }
        if (unit >= 1) {
            return colors[colors.length - 1];
        }

        float p = unit * (colors.length - 1);
        int i = (int) p;
        p -= i;

        // now p is just the fractional part [0...1) and i is the index
        int c0 = colors[i];
        int c1 = colors[i + 1];
        int a = ave(Color.alpha(c0), Color.alpha(c1), p);
        int r = ave(Color.red(c0), Color.red(c1), p);
        int g = ave(Color.green(c0), Color.green(c1), p);
        int b = ave(Color.blue(c0), Color.blue(c1), p);

        return Color.argb(a, r, g, b);
    }

    private static int calcResultColor(int squareColor, float sX, float lX) {
        float a = Color.alpha(squareColor);
        float r = Color.red(squareColor);
        float g = Color.green(squareColor);
        float b = Color.blue(squareColor);

//        final float px = 1 - squareX;
        final float px = sX;
        a = ave(a, 255, px);
        r = ave(r, 255, px);
        g = ave(g, 255, px);
        b = ave(b, 255, px);

        final float py = lX;
        a = ave(a, 255, py);
        r = ave(r, 0, py);
        g = ave(g, 0, py);
        b = ave(b, 0, py);

        return Color.argb(Math.round(a), Math.round(r), Math.round(g),
                Math.round(b));
    }

    @SuppressWarnings("unused")
    private int rotateColor(int color, float rad) {
        float deg = rad * 180 / Constant.PI;
        int r = Color.red(color);
        int g = Color.green(color);
        int b = Color.blue(color);

        ColorMatrix cm = new ColorMatrix();
        ColorMatrix tmp = new ColorMatrix();

        cm.setRGB2YUV();
        tmp.setRotate(0, deg);
        cm.postConcat(tmp);
        tmp.setYUV2RGB();
        cm.postConcat(tmp);

        final float[] a = cm.getArray();

        int ir = floatToByte(a[0] * r + a[1] * g + a[2] * b);
        int ig = floatToByte(a[5] * r + a[6] * g + a[7] * b);
        int ib = floatToByte(a[10] * r + a[11] * g + a[12] * b);

        return Color.argb(Color.alpha(color), pinToByte(ir), pinToByte(ig),
                pinToByte(ib));
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX() - mRingRadius - getPaddingLeft();
        float y = event.getY() - mRingRadius - getPaddingTop();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mInSaturRect = mSaturBarRealRect.contains((int)x, (int)y);
                mInLightRect = mLightBarRealRect.contains((int)x, (int)y);
                mTouchMoved = false;
                Log.i("pickerview", "ACTION_DOWN--mInSaturRect="+mInSaturRect+",x="+x);
                onTouchMoveBegin(x, y);
                mTouchDownX = x;
                mTouchDownY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                if (!mTouchMoved) {
                    if (Math.abs(x - mTouchDownX) >= mTouchSlope || Math.abs(y - mTouchDownY) >= mTouchSlope) {
                        mTouchMoved = true;
                        Log.i("pickerview", "move begin--mInSaturRect="+mInSaturRect+",x="+x);
                        onTouchMoveBegin(x, y);
                    }
                }
                if (mTouchMoved) {
                    Log.i("pickerview", "move begin--mInSaturRect="+mInSaturRect);
                    if (mInSaturRect) {
                        float xDiff = x - mLastSaturX;
                        if (xDiff != 0) {
                            setSaturationX(mSaturX + xDiff);
                        }
                        mLastSaturX = x;
                    } else if (mInLightRect) {
                        float xDiff = x - mLastLightX;
                        if (xDiff != 0 ) {
                            setLightnessX(mLightX + xDiff);
                        }
                        mLastLightX = x;
                    } else if (mTouchRectF.contains((int)x, (int)y) && isClickPreviewColorRegion(x, y)) {
                        float angle = (float) Math.atan2(y, x);
                        float angleDiff = angle - mLastRingAngle;
                        if (angleDiff != 0) {
                            setRingAngle(mRingAngle + angleDiff);
                        }
                        mLastRingAngle = angle;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                Log.i("pickerview", "move up--mTouchMoved="+mTouchMoved);
                if (!mTouchMoved) {
                    if (mInSaturRect) {
                        setSaturationX(mLastSaturX);
                    } else if (mInLightRect) {
                        setLightnessX(mLastLightX);
                    } else if (mTouchRectF.contains((int)x, (int)y) && isClickPreviewColorRegion(x, y)) {
                        setRingAngle(mLastRingAngle);
                    }
                }
                if (mListener != null) {
                    mListener.onColorClicked(mResultColor);
                }
                break;
        }
        return true;
    }
    
    private boolean isClickPreviewColorRegion(float x, float y) {
    	double distance = Math.sqrt((x - mOvalCenterX) * (x - mOvalCenterX) + (y - mOvalCenterY) * (y - mOvalCenterY));
    	if (mInnerRingRadius > distance) {
    		return false;
    	}
    	return true;
    }

    private void onTouchMoveBegin(float x, float y) {
        if (mInSaturRect) {
            mLastSaturX = x;
        } else if (mInLightRect) {
            mLastLightX = x;
        } else if (mTouchRectF.contains((int)x, (int)y) && isClickPreviewColorRegion(x, y)) {
            mLastRingAngle = (float) Math.atan2(y, x);
        }
    }

    private void setRingAngle(float angle) {
        if (angle < -Constant.PI) {
            angle += 2 * Constant.PI;
        } else if (angle > Constant.PI) {
            angle -= 2 * Constant.PI;
        }
        mRingAngle = angle;
        calcRingUnit(mRingAngle);
        onColorChanged();
    }

    private void setSaturationX(float sx) {
        sx = Utils.clamp(sx, mSaturBarRect.left, mSaturBarRect.right);
        mSaturX = sx;
        calcSquareUnit(mSaturX, mLightX);
        onColorChanged();
    }

    private void setLightnessX(float lx) {
        lx = Utils.clamp(lx, mLightBarRect.left, mLightBarRect.right);
        mLightX = lx;
        calcSquareUnit(mSaturX, mLightX);
        onColorChanged();
    }

    @SuppressWarnings("unused")
    private void setSquareXY(float sx, float lx) {
        sx = Utils.clamp(sx, mSaturBarRect.left, mSaturBarRect.right);
        lx = Utils.clamp(lx, mLightBarRect.left, mLightBarRect.right);
        mSaturX = sx;
        mLightX = lx;
        calcSquareUnit(mSaturX, mLightX);
        onColorChanged();
    }

    private void calcRingUnit(float angle) {
        // need to turn angle [-PI ... PI] into unit [0....1]
        float unit = angle / (2 * Constant.PI);
        if (unit < 0) {
            unit += 1;
        }
        mRingUnit = unit;
        mSquareColor = interpColor(sRingColors, unit);
        mResultColor = calcResultColor(mSquareColor, mSaturationRatio, mLightnessRatio);
        setSaturBarColor();
    }

    private void calcSquareUnit(float sx, float lx) {
        mSaturationRatio = (mSaturBarRect.right - sx) / mSaturBarRect.width();
        mLightnessRatio = (mLightBarRect.right - lx) / mLightBarRect.width();
        Log.i("pickerview", "calcSquareUnit() mSaturationRatio="+mSaturationRatio+",mLightnessRatio="+mLightnessRatio);
        mSaturationRatio = Utils.clamp(mSaturationRatio, 0, 1);
        mLightnessRatio = Utils.clamp(mLightnessRatio, 0, 1);
        mResultColor = calcResultColor(mSquareColor, mSaturationRatio, mLightnessRatio);
//        mResultColor = calcResultColor(mSquareColor, 0, 1);// for test
    }

    private void onColorChanged() {
        if (mListener != null) {
            mListener.colorChanged(mResultColor);
        }
        invalidate();
    }
}
